﻿const puppeteer = require('puppeteer');
//*****下句puppeteer模拟移动设备 内置插件（无需安装），用于抓取移动端网页
const devices = require('puppeteer/DeviceDescriptors')
const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
const {promisify}=require('util')
let log = (a) => {
    console.log(a);
};

const publicDir=['css','js','img']  //写上要创建的文件夹
fs.access('./public', fs.constants.F_OK, (err) => { //如果没有public目录则创建和创建上面三个目录
    if (err){
        fs.mkdir('./public', err => {
            if (err)throw err;
            publicDir.forEach((item) => {
                fs.mkdir('./public/'+item, (err) => {
                    if (err) throw err;
                })
            })
        });
    }else{
        console.log('已有文件夹');
    }
});

let zjfuweiUrl = 'https://www.xxxx.cn';
let zjRouters = [];  //接收路由表名
let urlArr = [];  //接收每页的静态资源文件地址
(async () => {
    //自定义一个等待事件方法，其实与page.waitFor(time)一样，只是有些地方不方便用page.waitFor(time)
    let sleep = time => new Promise(resolve => {
        setTimeout(resolve, time);
    });
    const browser = await puppeteer.launch({
        headless: false,
        slowMo: 250
    });   //获取浏览器对象
    const page = await browser.newPage();     //实例化一个窗口对象
    // await page.emulate(devices['iPhone 6'])   //*****移动端网页只需加上这句
    await page.goto(zjfuweiUrl, {waitUntil: 'networkidle2'}); //窗口跳转方法(网址)
    // *****抓取移动端网页别设置浏览器窗口大小
    await page.setViewport({  //设置浏览器窗口大小，太大或触发反爬规则，太小捕捉异步加载的内容比较少
        width: 1920,
        height: 1080
    });
    await sleep(500);
    //下句预防网站有301重定向跳转，go网址后刷新一次浏览器，但会让let obj=page.goto()，的obj监听事件出错。注意！
    await page.evaluate(() => {
        history.go(0);
    });
    await page.waitFor(2000);  //打开网站别太快操作，容易触发服务器反爬规则或防御
    let seveUrl=[];  //用来存所有静态资源文件地址最后才处理下载它们
    //这个page.on('response'窃听，只要page对象没有close  下面操作页面也会传给它窃听
    await page.on('response', response => {  //检测请求头url，也就是浏览器的NetWork
        //下句是只要后缀为jpg...就
        if (response.url().indexOf('.jpg') > -1 || response.url().indexOf('.png') > -1
            || response.url().indexOf('.js') > -1 || response.url().indexOf('.css') > -1) {
            if (response.url().indexOf('?')>-1) {
                const spl = response.url().split('?');
                urlArr.push(spl[0]);
                if(seveUrl.indexOf(spl[0])<0)
                    seveUrl.push(spl[0]);
            } else {
                urlArr.push(response.url());
                if(seveUrl.indexOf(response.url())<0)
                    seveUrl.push(response.url());
            }
        }
    });
    await sleep(1000);
    zjRouters = await page.evaluate(() => {
        var $ = window.$;
        var arr = [];
        // $('.mw-nav-item-link').each(function () {  //*****获取到移动端网页导航栏地址是这个class
        $('.w-nav-item-link').each(function () {  //获取到PC端网页导航栏地址是这个class
            arr.push($(this).attr('href'));
        });
        return arr;
    });
    await console.log(zjRouters);
    // await (()=>{ //测试时测试一个页面用
    //     zjRouters=['/sy'];
    // })()
    await (async () => {   //await自调函数，封装for遍历等待顺序执行
        for (let i = 0; i < zjRouters.length; i++) {  //里面有await用for别用forEach遍历
            //zjRouters是获取导航菜单所有按钮跳转地址的数组，逐一遍历go地址获取它们网页和静态资源
            let id = zjRouters[i];
            let url = zjfuweiUrl + id;
            urlArr = [];
            //下句打开网址会有个对象，接收了就能监听一些功能，与page.on()类型
            let urlResponse=await page.goto(url, {waitUntil: 'networkidle2'});
            await sleep(1000);

            await urlResponse.text()  //这个是puppeteer的api获取当前页面整个dom包括所有
                .then((data) => {
                    //then里如果是一个结果就不需return，如果是一个函数或方法就要return出去
                    return saveFile(urlArr,data)
                })
                .then((zjhtml) => {
                    // log(zjhtml.slice(0,2000))
                    const wPath=path.join(__dirname,id + '.html')
                    fs.writeFileSync(wPath, zjhtml, 'utf8');
                })
                .catch(err=>{
                log(err)
            })
        }
    })();
    //这里是专门下载静态资源，以及保存在对应类型目录，与saveHtml()处理dom内容href路径要对应
    await (async ()=>{
        for (let i = 0; i < seveUrl.length; i++) {
            const item = seveUrl[i];
            const mod = item.indexOf('https') > -1 ? https : http;
            let pname = path.basename(item);
            let typePath = path.extname(item).replace('.', '');
            if (typePath === 'png' || typePath === 'jpg') {
                typePath = 'img';
            }
            const dirPath = path.join('public', typePath, pname);
            const filePath = path.join(__dirname, dirPath);
            await mod.get(item, (res) => {   //请求一个个网络地址，取得res数据对象
                if (res.statusCode !== 200) log(item+'这里断了线')
                res.pipe(fs.createWriteStream(filePath)) //用流方式写入
                    .on('close', () => {
                        log(filePath);
                    });
            });
            await sleep(3000)
        }
    })();
    await browser.close();
})();
//这里处理网页dom内容(将href网站换成本地public目录)，写成文件分工下一步做
function saveFile(urlArr, zjhtml) {
    return new Promise(async (resolve, reject) => {
        if(!zjhtml||urlArr.length<1){
            reject('没有传资源链接或html内容没传来');
            return
        }
        for (let i = 0; i < urlArr.length; i++) {  //urlArr是读取静态资源网络地址，集成的数组
            let item = urlArr[i];
            //下面将网络资源路径https://www.xxxx.cn/ab/626525.png转为本
            // 地 ./public/xxtype/文件名和后缀
            let pname = path.basename(item);
            let typePath = path.extname(item).replace('.', '');
            if (typePath === 'png' || typePath === 'jpg') {
                typePath = 'img';
            }
            const dirPath = path.join('public', typePath, pname);
            let dirSpl = dirPath.split("\\").join('/');
            if (zjhtml.indexOf(item) > -1) {  //如果html代码匹配准确网络地址直接替换
                log('indexOf 0');
                let reg = new RegExp(item, 'g');  //替换只有全局g正则才能替换所有，否则只能替换一个
                zjhtml = zjhtml.replace(reg, './' + dirSpl);
            } else {   //否则html代码可能给改了//www.zjfuwei就把网络地址变成//开头
                let splUrl = item.split('//');
                log(splUrl[1]);
                let reg = new RegExp('//' + splUrl[1], 'g');
                zjhtml = zjhtml.replace(reg, './' + dirSpl);
            }
        }
        resolve(zjhtml);
    });
}

